<?php

namespace Modules\Common\Models;


use Illuminate\Database\Concerns\BuildsQueries;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Modules\Common\Exceptions\Common\ModelNotFoundException;
use Modules\Common\Exceptions\Common\ValidationException;
use Illuminate\Support\Arr;
use Modules\Common\Models\Helpers\TrashManager;
use phpDocumentor\Reflection\Types\Self_;

/**
 * 安全的模型增删改,在输入不可信任的情况下使用
 * 内部使用set方法进行赋值操作，使得模型上定义的修改器可以生效
 * @method static \Illuminate\Database\Eloquent\Builder where($column, $operator = null, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Eloquent\Builder whereKey($column, $operator = null, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Eloquent\Builder|static orWhere($column, $operator = null, $value = null)
 * @method static \Illuminate\Database\Eloquent\Builder latest($column = null)
 * @method static \Illuminate\Database\Eloquent\Builder oldest($column = null)
 * @method static \Illuminate\Database\Eloquent\Model|static|null find($id, $columns = ['*'])
 * @method static \Illuminate\Database\Eloquent\Collection findMany($ids, $columns = ['*'])
 * @method static \Illuminate\Database\Eloquent\Collection|static[] get($columns = ['*'])
 * @method static \Illuminate\Contracts\Pagination\LengthAwarePaginator paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
 * @method static \Illuminate\Contracts\Pagination\Paginator simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
 * @method static \Illuminate\Database\Eloquent\Model with($relations)
 * @method static \Illuminate\Database\Query\Builder from($table, $as = null)
 * @method static \Illuminate\Database\Query\Builder select(...$columns = ['*'])
 * @method static \Illuminate\Database\Query\Builder|static selectRaw($expression, array $bindings = [])
 * @method static \Illuminate\Database\Query\Builder|static join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
 * @method static \Illuminate\Database\Query\Builder|static joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false)
 * @method static \Illuminate\Database\Query\Builder|static leftJoin($table, $first, $operator = null, $second = null)
 * @method static \Illuminate\Database\Query\Builder|static rightJoin($table, $first, $operator = null, $second = null)
 * @method static \Illuminate\Database\Query\Builder|static whereColumn($first, $operator = null, $second = null, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder whereRaw($sql, $bindings = [], $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder whereIn($column, $values, $boolean = 'and', $not = false)
 * @method static \Illuminate\Database\Query\Builder whereNotIn($column, $values, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder whereBetween($column, array $values, $boolean = 'and', $not = false)
 * @method static \Illuminate\Database\Query\Builder|static whereNotBetween($column, array $values, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder|static whereDate($column, $operator, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder|static whereTime($column, $operator, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder|static whereDay($column, $operator, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder|static whereMonth($column, $operator, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder|static whereYear($column, $operator, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Query\Builder|static orderBy($column, $direction = 'asc')
 * @method static \Illuminate\Database\Query\Builder|static orderByDesc($column)
 * @method static \Illuminate\Support\Collection pluck($column, $key = null)
 * @method static count($columns = '*')
 * @method static min($column)
 * @method static max($column)
 * @method static sum($column)
 * @method static avg($column)
 * @method static average($column)
 */
class BaseModel extends Model
{
    use SoftDeletes;

    protected $dates = ['created_at', 'updated_at', 'deleted_at'];
    protected $guarded = [];

    protected $dispatchesEvents = [
        'deleted' => TrashManager::class,
    ];

    /**
     * @param $data
     * @return static
     */
    public static function createFromArray($data)
    {
        $instance = new static();
        $instance->validateCreateParams($data);
        return self::performSave($instance, $data);
    }

    /**
     * 从请求参数中选取指定的字段创建记录，并为空的请求参数提供默认值
     * @param  array $allowedKeys 请求的字段名称
     * @param  array $defaults 指定值（键值对），请求参数中不存在或为null的情况下将使用指定值
     * @return static
     * @throws
     */
    public static function safeCreateWithRequestParams(array $allowedKeys, array $defaults = [])
    {
        $allParams = [];
        foreach ($allowedKeys as $key) {
            $requestParam = request($key, $defaults[$key] ?? null);
            if (is_null($requestParam)) {
                ValidationException::new()
                    ->tellUser(sprintf('字段%s不能为空', $key))
                    ->logNotice(sprintf('字段%s为空', $key))
                    ->throw();
            }
            $allParams[$key] = $requestParam;
        }
        foreach ($defaults as $key => $default) {
            if (!isset($allParams[$key])) {
                $allParams[$key] = $default;
            }
        }
        $instance = new static();
        $instance->validateCreateParams($allParams);
        return self::performSave($instance, $allParams);
    }

    public static function updateFromArray($data, $withDeleted = false)
    {
        if ($withDeleted) {
            $instance = static::withTrashed()->find($data['id']);
        } else {
            $instance = static::find($data['id']);
        }

        if (is_null($instance)) {
            ModelNotFoundException::new()
                ->tellUser('记录不存在！')
                ->logNotice('试图修改不存在记录！', ['model' => static::class, 'request' => request()->all()])
                ->throw();
        }
        $allParams = Arr::except($data, ['id']);
        foreach ($allParams as $k => $v) {
            $instance->{$k} = $v;
        }
        $instance->validateUpdateParams($instance->getAttributes());
        $instance->save();
        return $instance;
    }


    /**
     * 使用请求参数来更新记录，注意，只能更新没有业务约束的字段
     * @param $allowedKeys
     * @param  null $id
     * @return Model|static|null
     * @throws
     */
    public static function safeUpdateWithRequestParams($allowedKeys, $id = null)
    {
        $id = $id ?: request('id');
        $instance = static::find($id);
        if (is_null($instance)) {
            ModelNotFoundException::new()
                ->tellUser('记录不存在！')
                ->logNotice('试图修改不存在记录！', ['model' => static::class, 'request' => request()->all()])
                ->throw();
        }
        $allParams = [];
        foreach ($allowedKeys as $key) {
            if (!is_null($requestParam = request($key))) {
                $allParams[$key] = $requestParam;
            }
        }
        foreach ($allParams as $k => $v) {
            $instance->{$k} = $v;
        }
        $instance->validateUpdateParams($instance->getAttributes());
        $instance->save();
        return $instance;
    }

    public static function safeDelete($id)
    {
        $instance = static::find($id);
        if ($instance == null) {
            ModelNotFoundException::new()
                ->tellUser('记录不存在！')
                ->logNotice('试图删除不存在记录！', ['table' => $instance->getTable(), 'id' => $id])
                ->throw();
        }
        $instance->delete();
    }

    public static function encryptMultiId(array $ids)
    {
        return Crypt::encrypt(implode(',', $ids) . '-' . Str::random(5));
    }

    public static function encryptId($fileId)
    {
        return Crypt::encrypt($fileId . '-' . Str::random(5));
    }

    public static function decryptMultiId($encryptedId)
    {
        try {
            $decrypt = Crypt::decrypt($encryptedId);
            $decrypt = Str::before($decrypt, '-');
            return explode(',', $decrypt);
        } catch (\Throwable $e) {
            ValidationException::new()
                ->tellUser('不是有效的加密串')
                ->logNotice('模型decryptId失败', ['$encryptedId' => $encryptedId])
                ->throw();
        }
    }

    public static function decryptId($encryptedId)
    {
        try {
            $decrypt = Crypt::decrypt($encryptedId);
            $decrypt = (int)Str::before($decrypt, '-');
        } catch (\Throwable $e) {
            ValidationException::new()
                ->tellUser('不是有效的加密串')
                ->logNotice('模型decryptId失败', ['$encryptedId' => $encryptedId])
                ->throw();
        }
        return (int)Str::before($decrypt, '-');
    }

    public static function hasJoined(Builder $query, $table)
    {
        foreach ($query->getQuery()->joins as $join) {
            if ($join->table === $table) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param $instance
     * @param $data
     * @return static
     */
    protected static function performSave($instance, $data)
    {
        foreach ($data as $k => $v) {
            $instance->{$k} = $v;
        }
        $instance->save();
        return $instance;
    }

    protected function validateCreateParams($data)
    {
        $this->validate(static::createRules(), $data);
    }

    protected function validateUpdateParams($updates)
    {
        $this->validate(static::updateRules(), $updates);
    }

    protected function validate($validation, $data)
    {
        $rules = $validation['rules'];


        $messages = $validation['messages'] ?? [];
        $customAttributes = $validation['customAttributes'] ?? [];
        $validator = Validator::make($data, $rules, $messages, $customAttributes);

        if ($validator->fails()) {
            $errors = $validator->errors()->getMessages();

            $msg = array();
            if ($errors) {
                $class = static::class;
                $m = new $class();
                foreach ($errors as $k => $v) {
                    $comment = DB::selectOne("select `COLUMN_COMMENT` from `information_schema`.`COLUMNS` where `TABLE_SCHEMA`='"
                        . config('database.connections.mysql.database') . "' and `COLUMN_NAME`='" . $k . "' and `TABLE_NAME`='"
                        . $m->getTable() . "'");
                    $msg[] = ($comment ? $comment->COLUMN_COMMENT : $k) . '：' . implode(',', $v);
                }
            }

            ValidationException::new()
                ->tellUser(implode('；', $msg))
                ->logNotice('模型参数校验不通过！', ['errors' => $validator->errors()->getMessages(), 'params' => $data, 'class' => static::class])
                ->throw();
        }
    }
}
